#include "../../network/src/tcpconnection.h"
#include "../../network/src/tcpacceptor.h"
#include "tcpconnectionpool.h"
#include "netservice.h"
#include <boost/bind.hpp>

NetService::NetService(AsyncService &service) :
    m_service(service)
{

}

NetService::~NetService()
{

}

int NetService::initialize()
{
    Mutex::scoped_lock acceptorLock(m_acceptorLock);

    if(m_acceptor.get()){
        return boost::system::errc::file_exists;
    }

    m_acceptor = AcceptorPtr(new TcpAcceptor(m_service.implement()));

    if(!m_acceptor.get()){
        return boost::system::errc::not_enough_memory;
    }

    return 0;
}

int NetService::open(const std::string &host, unsigned short port, int listenCount)
{
    Mutex::scoped_lock acceptorLock(m_acceptorLock);

    if(!m_acceptor.get()){
        return boost::system::errc::bad_file_descriptor;
    }

    return m_acceptor->open(host, port, listenCount);
}

void NetService::close()
{
    {
        Mutex::scoped_lock acceptorLock(m_acceptorLock);

        if(m_acceptor.get()){
            m_acceptor->close();
        }
    }

    {
        Mutex::scoped_lock cmLock(m_cmLock);

        while(!m_connectionMap.empty()){
            UnorderedMap::iterator it
                    = m_connectionMap.begin();

            it->second->close();

            {
                Mutex::scoped_lock trashLock(m_trashLock);

                m_trash.insert(it->second);

                m_service.post
                        (
                            boost::bind
                            (
                                &NetService::onTrash,
                                this
                            )
                        );
            }

            m_connectionMap.erase(it);
        }
    }
}

void NetService::close(unsigned long key)
{
    Mutex::scoped_lock cmLock(m_cmLock);

    UnorderedMap::iterator it = m_connectionMap.find(key);

    if(it != m_connectionMap.end()){
        it->second->close();

        {
            Mutex::scoped_lock trashLock(m_trashLock);

            m_trash.insert(it->second);

            m_service.post
                    (
                        boost::bind
                        (
                            &NetService::onTrash,
                            this
                        )
                    );
        }

        m_connectionMap.erase(it);
    }
}

int NetService::endpoint(unsigned long key, std::string &host, unsigned short &port)
{
    ConnectionPtr con;

    {
        Mutex::scoped_lock cmLock(m_cmLock);

        UnorderedMap::iterator it
                = m_connectionMap.find(key);

        if(it != m_connectionMap.end()){
            con = it->second;
        }
    }

    if(con.get()){
        return con->remoteEndpoint(host, port);
    }

    return boost::system::errc::bad_file_descriptor;
}

void NetService::accept(BOOST_ASIO_MOVE_ARG(Accepter) a)
{
    {
        Mutex::scoped_lock acceptorLock(m_acceptorLock);

        if(!m_acceptor.get()){
            a(0, boost::system::errc::not_connected);
            return;
        }
    }

    ConnectionPtr con = m_connectionPool.alloc(m_service.implement());

    if(!con.get()){
        a(0, boost::system::errc::not_enough_memory);
        close();
        return;
    }

    m_acceptor->asyncAccept
            (
                con->sock(),
                boost::bind
                (
                    &NetService::onAccepted,
                    this,
                    Accepter(a),
                    con,
                    _1
                )
            );
}

void NetService::read(unsigned long key, void *buf, size_t size,
                      BOOST_ASIO_MOVE_ARG(Filter) f,
                      BOOST_ASIO_MOVE_ARG(Completion) c)
{
    ConnectionPtr con;

    {
        Mutex::scoped_lock cmLock(m_cmLock);

        UnorderedMap::iterator it = m_connectionMap.find(key);

        if(it != m_connectionMap.end()){
            con = it->second;
        }
    }

    if(!con.get()){
        c(0, 0, boost::system::errc::bad_file_descriptor);
        return;
    }

    con->asyncRead
            (
                buf,
                size,
                boost::bind
                (
                    &NetService::isComplete,
                    this,
                    Filter(f),
                    _1
                ),
                boost::bind
                (
                    &NetService::onComplete,
                    this,
                    Completion(c),
                    con,
                    _1,
                    _2
                )
            );
}

void NetService::write(unsigned long key, const void *buf, size_t size,
                       BOOST_ASIO_MOVE_ARG(Filter) f,
                       BOOST_ASIO_MOVE_ARG(Completion) c)
{
    ConnectionPtr con;

    {
        Mutex::scoped_lock cmLock(m_cmLock);

        UnorderedMap::iterator it = m_connectionMap.find(key);

        if(it != m_connectionMap.end()){
            con = it->second;
        }
    }

    if(!con.get()){
        c(0, 0, boost::system::errc::bad_file_descriptor);
        return;
    }

    con->asyncWrite
            (
                buf,
                size,
                boost::bind
                (
                    &NetService::isComplete,
                    this,
                    Filter(f),
                    _1
                ),
                boost::bind
                (
                    &NetService::onComplete,
                    this,
                    Completion(c),
                    con,
                    _1,
                    _2
                )
            );
}

void NetService::onAccepted(Accepter a, ConnectionPtr con, int err)
{
    if(err){
        a(0, err);
        return;
    }

    {
        Mutex::scoped_lock cmLock(m_cmLock);

        m_connectionMap.insert
                (
                    UnorderedMap::value_type((unsigned long)con.get(), con)
                );
    }

    a((unsigned long)con.get(), 0);
}

size_t NetService::isComplete(Filter f, size_t size)
{
    return f(size);
}

void NetService::onComplete(Completion c, ConnectionPtr con, size_t size, int err)
{
    if(err){
        close((unsigned long)con.get());
    }

    c((unsigned long)con.get(), size, err);
}

void NetService::onTrash()
{
    Mutex::scoped_lock trashLock(m_trashLock);

    if(m_trash.empty()){
        return;
    }

    for(UnorderedSet::iterator it = m_trash.begin();
        it != m_trash.end(); ++it){

        if((*it).unique()){
            m_trash.erase(it);
            break;
        }
    }

    m_service.post
            (
                boost::bind
                (
                    &NetService::onTrash,
                    this
                )
            );
}
